Elasticsearch

Mapping

Le mapping correspond au schéma des tables en SQL classique.

Comment ça se passe ?

Pour récupérer le mapping d'un type

$ curl -X GET :9200/index/type/_mapping

Utiliser un mapping explicite permet d'avoir de meilleure perf en insert. Ca permet également de

  • réduire la taille de l'index sur le disque
  • indexer uniquement les champs important
  • préparer les données pour une recherche + rapide or real time analitycs.

Exemple de mapping

"orders" : {
  "properties" : {
    "id": {
      "type": "string",
      "store": "yes",
      "index": "not_analyzed"
  }
}

Lorsqu'on crée un mapping, on peut également donner à elastic des hints sur la méthode pour mieux gérer les champs.

Voici les options qu'il y a sur un champ :

  • store : par défaut à non, indique si le champ doit être sauvegardé dans un index fragmenté pour un fast retrieving. Ca coute sur le disque mais ça permet de récupérer le champ beaucoup plus rapidement
  • index : Voir après pour la manière dont est indexé un champ
    • no : Possibilité de ne pas indexer les data, elles ne seront pas cherchable alors.
    • analyzed : Analysé avec un analyseur configuré
    • not_analyzed : CE field est traité et indexé sans être analysé.
  • boost : Permet de changer l'importance du champ (1/0)
  • index_analyzer : Permet de donner un analyseur particulier , si vide utilise celui du parent.
  • search_analyzer : Permet de définir un analyseur particulier pdt la recherche.
  • include_in_all : inclut ce champ dans le champ _all.

On a également la possibilité de pouvoir utiliser un template dynamique pour le mapping des documents.

Comment est indexé un champ ?

La manière dont l'index se crée dépend, du type d'analyzeur choisit pendant le mapping.

Admettons qu'on veuille indexer cette phrase :

Peter's house is big.

Type d'analyzer au mapping Analyzer Tokens
Pas de mapping Pas d'index Pas de token
not_alanyzed KeywordAnalyzer [Peter's house is big]
analyzed StandardAnalyzer ["peter", "s", "house", "is", "big"]

On constate donc que le,

  • keywordAnalyzer qui est utilisé par défaut pour le champ not_analyzed, sauvegarde le texte de manière inchangé comme un token.
  • standardAnalyzer valeur par défaut pour le analyzed, va tokenizer chaque en fonction des espaces et de la ponctuation. Chaque token est converti en minuscule, ce qui signifie qu'on pourra chercher peter mais pas Peter.

Structurer correctement les données peut permettre des gains de perf assez conséquent.

Mapper des objets nesté (un contenu dans l'autre)

A la base tout les champs contenu dans un objet sont vu comme un seul et même objet et il n'est pas possible de distinguer les valeurs entre différent objet encapsulés dans un même tableau.

Heureusement elastic nous permet graĉe à un attribut de faire cette distinction :

Exemple :

"orders" : {
  "properties" : {
      "id": {
        "type": "string",
        "store": "yes",
        "index": "not_analyzed"
      }, 
      "test" : {
        "type": "nested", 
        "properties" : {
          "name:" : { "type": "string", "store":"no"  }
        }
      }
  }
}

D'une manière assez simple si un document est marqué comme "nested", il va être extrait du document original et indexé comme un document externe. Les nested object ne peuvent pas être recherché à l'aide de query classique, uniquement avec des nested query.

GeoPoint Field

Pour définir un point géographique rien de plus simple :

"orders" : {
  "properties" : {
    "customer_location": {
      "type": "geo_point",
      "store": "yes"
  }
}

Updater un mapping

$ curl -XPUT 'http://localhost:9200/twitter/_mapping/tweet' -d '
{
    "tweet" : {
        "properties" : {
            "message" : {"type" : "string", "store" : true }
        }
    }
}'

Configurer un analyseur particulier en français :

"settings": {
    "analysis": {
      "filter": {
        "elision": {
          "type": "elision",
          "articles": ["l", "m", "t", "qu", "n", "s", "j", "d"]
        }
      },
      "analyzer": {
        "custom_french_analyzer": {
          "tokenizer": "letter",
          "filter": ["asciifolding", "lowercase", "french_stem", "elision", "stop"]
        },
        "tag_analyzer": {
          "tokenizer": "keyword",
          "filter": ["asciifolding", "lowercase"]
        },
      }
    }
  },

Analyseur francais relativement complet :

Lien vers l'analyseur fr

{
  "settings": {
    "analysis": {
      "filter": {
        "french_elision": {
        "type":         "elision",
            "articles": [ "l", "m", "t", "qu", "n", "s",
                          "j", "d", "c", "jusqu", "quoiqu",
                          "lorsqu", "puisqu"
                        ]
        },
        "french_stop": {
          "type":       "stop",
          # Peut être remplacé par "_french_"
          "stopword" : ["ès", "vers", "a", "à", "afin", "ai", "ainsi", "après", "attendu", "au", "aujourd", "auquel", "aussi", "autre", "autres", "aux", "auxquelles", "auxquels", "avait", "avant", "avec", "avoir", "c", "ça", "car", "ce", "ceci", "cela", "celle", "celles", "celui", "cependant", "certain", "certaine", "certaines", "certains", "ces", "cet", "cette", "ceux", "chez", "ci", "combien", "comme", "comment", "concernant", "contre", "d", "dans", "de", "debout", "dedans", "dehors", "delà", "depuis", "derrière", "des", "dès", "désormais", "desquelles", "desquels", "dessous", "dessus", "devant", "devers", "devra", "divers", "diverse", "diverses", "doit", "donc", "dont", "du", "duquel", "durant", "elle", "elles", "en", "entre", "environ", "est", "et", "etc", "été", "etre", "être", "eu", "eux", "excepté", "hélas", "hormis", "hors", "hui", "il", "ils", "j", "je", "jusqu", "jusque", "l", "la", "là", "laquelle", "le", "lequel", "les", "lesquelles", "lesquels", "leur", "leurs", "lorsque", "lui", "ma", "mais", "malgré", "me", "même", "mêmes", "merci", "mes", "mien", "mienne", "miennes", "miens", "moi", "moins", "mon", "moyennant", "n", "ne", "néanmoins", "ni", "non", "nos", "notre", "nôtre", "nôtres", "nous", "ô", "on", "ont", "ou", "où", "outre", "par", "parmi", "partant", "pas", "passé", "pendant", "plein", "plus", "plusieurs", "pour", "pourquoi", "près", "proche", "puisque", "qu", "quand", "que", "quel", "quelle", "quelles", "quels", "qui", "quoi", "quoique", "revoici", "revoilà", "s", "sa", "sauf", "se", "selon", "seront", "ses", "si", "sien", "sienne", "siennes", "siens", "sinon", "soi", "soit", "son", "sont", "sous", "suivant", "sur", "ta", "te", "tes", "tien", "tienne", "tiennes", "tiens", "toi", "ton", "tous", "tout", "toute", "toutes", "tu", "un", "une", "va", "voici", "voilà", "vos", "votre", "vôtre", "vôtres", "vous", "vu", "y"]
        },
        # Les mots qui vont être exclu du stemmer, à remove si on veut pas en exclure
        "french_keywords": {
          "type":       "keyword_marker",
          "keywords":   [] 
        },
        "french_stemmer": {
          "type":       "stemmer",
          "language":   "light_french"
        }
      },
      "analyzer": {
        "french": {
          "tokenizer":  "standard",
          "filter": [
            "french_elision",
            "lowercase",
            "french_stop",
            "french_keywords",
            "french_stemmer"
          ]
        }
      }
    }
  }
}

Synonymes

Plus d'infos

Possibilité de directement le setup depuis le JSON ou via un fichier txt.

 PUT /my_index
{
  "settings": {
    "analysis": {
      "filter": {
        "my_synonym_filter": {
          "type": "synonym", 
          "synonyms": [ 
            "british,english",
            "queen,monarch"
          ]
        }
      },
      "analyzer": {
        "my_synonyms": {
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "my_synonym_filter" 
          ]
        }
      }
    }
  }
}

A l'aide d'un fichier texte reprenant le Solr synonym system file :

# blank lines and lines starting with pound are comments.

#Explicit mappings match any token sequence on the LHS of "=>"
#and replace with all alternatives on the RHS.  These types of mappings
#ignore the expand parameter in the schema.
#Examples:
i-pod, i pod => ipod,
sea biscuit, sea biscit => seabiscuit

#Equivalent synonyms may be separated with commas and give
#no explicit mapping.  In this case the mapping behavior will
#be taken from the expand parameter in the schema.  This allows
#the same synonym file to be used in different synonym handling strategies.
#Examples:
ipod, i-pod, i pod
foozball , foosball
universe , cosmos

# If expand==true, "ipod, i-pod, i pod" is equivalent
# to the explicit mapping:
ipod, i-pod, i pod => ipod, i-pod, i pod
# If expand==false, "ipod, i-pod, i pod" is equivalent
# to the explicit mapping:
ipod, i-pod, i pod => ipod

#multiple synonym mapping entries are merged.
foo => foo bar
foo => baz
#is equivalent to
foo => foo bar, baz

En utilisant le mapping suivant :

{
    "index" : {
        "analysis" : {
            "analyzer" : {
                "synonym" : {
                    "tokenizer" : "whitespace",
                    "filter" : ["synonym"]
                }
            },
            "filter" : {
                "synonym" : {
                    "type" : "synonym",
                    "synonyms_path" : "analysis/synonym.txt"
                }
            }
        }
    }
}

Exemple

$ curl -XPUT localhost:9200/geoloc -d '{
  "settings": {
      "index": {
        "analysis": {
          "filter": {
            "french_elision": {
            "type": "elision",
            "articles": [ "l", "m", "t", "qu", "n", "s",
                  "j", "d", "c", "jusqu", "quoiqu",
                  "lorsqu", "puisqu"
                ]
            },
            "french_stop": {
              "type": "stop",
              "stopword" : ["ès", "vers", "a", "à", "afin", "ai", "ainsi", "après", "attendu", "au", "aujourd", "auquel", "aussi", "autre", "autres", "aux", "auxquelles", "auxquels", "avait", "avant", "avec", "avoir", "c", "ça", "car", "ce", "ceci", "cela", "celle", "celles", "celui", "cependant", "certain", "certaine", "certaines", "certains", "ces", "cet", "cette", "ceux", "chez", "ci", "combien", "comme", "comment", "concernant", "contre", "d", "dans", "de", "debout", "dedans", "dehors", "delà", "depuis", "derrière", "des", "dès", "désormais", "desquelles", "desquels", "dessous", "dessus", "devant", "devers", "devra", "divers", "diverse", "diverses", "doit", "donc", "dont", "du", "duquel", "durant", "elle", "elles", "en", "entre", "environ", "est", "et", "etc", "été", "etre", "être", "eu", "eux", "excepté", "hélas", "hormis", "hors", "hui", "il", "ils", "j", "je", "jusqu", "jusque", "l", "la", "là", "laquelle", "le", "lequel", "les", "lesquelles", "lesquels", "leur", "leurs", "lorsque", "lui", "ma", "mais", "malgré", "me", "même", "mêmes", "merci", "mes", "mien", "mienne", "miennes", "miens", "moi", "moins", "mon", "moyennant", "n", "ne", "néanmoins", "ni", "non", "nos", "notre", "nôtre", "nôtres", "nous", "ô", "on", "ont", "ou", "où", "outre", "par", "parmi", "partant", "pas", "passé", "pendant", "plein", "plus", "plusieurs", "pour", "pourquoi", "près", "proche", "puisque", "qu", "quand", "que", "quel", "quelle", "quelles", "quels", "qui", "quoi", "quoique", "revoici", "revoilà", "s", "sa", "sauf", "se", "selon", "seront", "ses", "si", "sien", "sienne", "siennes", "siens", "sinon", "soi", "soit", "son", "sont", "sous", "suivant", "sur", "ta", "te", "tes", "tien", "tienne", "tiennes", "tiens", "toi", "ton", "tous", "tout", "toute", "toutes", "tu", "un", "une", "va", "voici", "voilà", "vos", "votre", "vôtre", "vôtres", "vous", "vu", "y"]
            },
            "french_stemmer": {
              "type":       "stemmer",
              "language":   "light_french"
            },
            "synonym": {
                "type":"synonym",
                "synonyms": [
                    "cie, compagnie",
                    "mme, madame, mam",
                    "mr, monsieur, msieur"
                ]
            }
          },
          "analyzer": {
            "french": {
              "tokenizer":"standard",
              "filter": [
                "french_elision",
                "lowercase",
                "french_stop",
                "french_stemmer",
                "synonym"
              ]
            }
          }
        }
      }
    }
}'

Multi-fields

Depuis la version 1.5, le type multi-field est supprimé au profit d'un paramètre field.


# < 1.5

"title": {
    "type": "multi_field",
    "fields": {
        "title": { "type": "string" },
        "raw":   { "type": "string", "index": "not_analyzed" }
    }
}

# >= 1.5

"title": {
    "type": "string",
    "fields": {
        "raw":   { "type": "string", "index": "not_analyzed" }
    }
}